要開發node.js程式,還是需要知道一些基本知識,才容易上手。
event loop
Javascript執行的life cycle,大致可以分成兩個部份:
通常global scope的程式只會執行一次(不過在瀏覽器中,可以利用動態新增script tag來進入載入的javascript的global scope),執行完畢後,就進入event loop,執行所有被觸發的事件處理函數。事件處理函數被觸發以後,會排進一個queue結構,透過一個loop,從queue裡面取出被觸發的函數來執行。
不論在瀏覽器或是在伺服器端執行Javascript,運行的機制都是這樣。所以雖然可以非同步執行,但是其實同時只會有一個函數在執行,而且一個函數執行完畢之前,其他函數都不會執行。這樣的結構有一些好處,除了避開像Thread這樣平行處理的複雜度(同時不會有兩個函數執行,所以共用的變數不需要鎖定),也減少了建立Thread所需要得額外成本,所以反應速度很快。
但是Javascript使用的event loop結構也有一些缺點,首先,只要有一個函數執行時間比較久,就會影響系統的反應速度。另外,這樣的結構基本上只能在單一執行緒中執行,在目前CPU幾乎都是多核心的環境下,很難發揮最好的效能。
目前要解決效能問題的方式,通常都是依照系統可使用的核心數,來同時執行幾個伺服器程式,然後在前方利用Reverse Proxy來做負載均衡,把負載分散到各個獨立執行的伺服器程式。不過最近看到Intel有計劃在發展有多核心執行能力的Javascript引擎,也許過不久也會發展出可以「無痛」利用所有核心的node.js伺服器。(https://github.com/RiverTrail/RiverTrail )
commonjs模組標準
參考:http://www.commonjs.org/specs/modules/1.0/
簡單地說,在模組中,你可以透過exports變數把做好的API輸出到用require(identifier)來引入指定模組的程式來使用。用簡單的程式來看會更清楚:
模組程式mod.js:
exports.add = function(a, b) {return a+b;};
那要在程式中使用模組,可以這樣做:
var add = require(mod).add;
console.log(add(1,2));
//顯示3
在node.js中,除了使用exports,還可以利用module.exports來直接輸出API。例如上面的範例可以改成:
module.exports = function(a, b) {return a+b;};
使用起來更簡單:
var add = require(mod);
console.log(add(1,2));//結果一樣
另外要補充一點,除了昨天在環境配置中提到的使用NODE_PATH來指定預設的模組路徑,在應用程式所在位置建立一個叫做node_modules的子目錄,也有同樣的效果。node會在這些目錄中尋找模組,這樣就可以省去搜尋模組檔案的麻煩。
另外,如果使用到多個檔案,通常需要用目錄而不是單一檔案來放置模組。這時候可以用目錄名稱當作identtifier來載入模組,但是需要符合幾個規則:
node會使用一定的規則來搜尋、載入要使用的模組。詳細說明可以參考:http://nodejs.org/docs/v0.5.9/api/modules.html
node.js的Global環境
Javascript除了在ECMA-262中定義的核心物件外,所能使用的函數及物件,都是透過global環境取得的。(當然,有了模組機制後,就不需要這麼麻煩)所以先了解一下到底node.js的global環境提供了什麼,會很有幫助。
node.js主要是伺服器端的執行環境,比較不必擔心一些在瀏覽器環境中需要考慮的安全問題。所以在node.js中使用for...in敘述來做reflection,通常不會有太大問題。像這樣就可以輕鬆知道global環境有什麼可以使用的物件及函數:
(function(obj){for(var i in obj){console.log(i+': '+ typeof obj[i]);})(this);
在node的console跑跑看(執行node時不加參數):
> (function(obj){for(var i in obj){console.log(i + ': ' + typeof obj[i]);}})(this);
root: object
require: function
Float64Array: function
Uint32Array: function
Uint16Array: function
clearTimeout: function
Int32Array: function
global: object
ArrayBuffer: function
Int16Array: function
DataView: function
Float32Array: function
Int8Array: function
setInterval: function
console: object
clearInterval: function
GLOBAL: object
module: object
Buffer: function
Uint8Array: function
setTimeout: function
process: object
不過如果跟API文件中的說明對照一下(http://nodejs.org/docs/v0.5.9/api/globals.html ),還是缺了幾個東西:
__filename
__dirname
exports
簡單說明一下:
* root, global, GLOBAL都是global物件的別名,就像在瀏覽器環境中的window物件一樣。
* ArrayBuffer, Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, DataView等,都是最近才加到開發版中的TypedArray相關物件
* process就是Process物件,用來提供一些工具程式以及行程資訊
* Buffer物件,是node.js用來處理binary資料的物件
* console是node.js用來處理stdio的物件
* setTimeout, clearTimeout, setInterval, clearInterval,與瀏覽器中的同名函數使用方式差不多
* __filename,就是執行的js程式檔名,如果是在模組裡面,就是模組檔案的檔名,而不是主程式的檔名
* __dirname,同上,不過是js程式檔目錄的目錄名
API本身的篇幅較長,還是放到明天再來介紹,順便寫一個簡單偵測API的程式。